package jmine.tec.web.wicket.pages.form;

import static jmine.tec.report.impl.table.RendererParameterInterface.PARAM_HIDDEN;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_DELETE;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_EDIT;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_VIEW;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.INFO_ENTITY_DELETE_SUCCESS;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.LABEL_SEARCH_RESULT;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.LABEL_SEARCH_TITLE;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jmine.tec.persist.PersistMessages;
import jmine.tec.report.impl.ReportBuilder;
import jmine.tec.report.impl.table.ReportTableBuilder;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import jmine.tec.web.wicket.component.command.TableCommandsProvider;
import jmine.tec.web.wicket.component.command.button.ButtonCommand;
import jmine.tec.web.wicket.component.command.button.ButtonCommandsPanel;
import jmine.tec.web.wicket.component.command.button.NewCommand;
import jmine.tec.web.wicket.component.command.button.NewCommandHandler;
import jmine.tec.web.wicket.component.command.button.SearchCommand;
import jmine.tec.web.wicket.component.command.button.SearchCommandHandler;
import jmine.tec.web.wicket.component.command.img.DeleteCommand;
import jmine.tec.web.wicket.component.command.img.DeleteCommandHandler;
import jmine.tec.web.wicket.component.command.img.EditCommand;
import jmine.tec.web.wicket.component.command.img.EditCommandHandler;
import jmine.tec.web.wicket.component.command.img.ImageCommand;
import jmine.tec.web.wicket.component.command.img.ViewCommand;
import jmine.tec.web.wicket.component.command.img.ViewCommandHandler;
import jmine.tec.web.wicket.component.table.SearchResultTable;
import jmine.tec.web.wicket.spring.SpringBeanReference;

import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.CompoundPropertyModel;

import bancosys.tec.exception.BusinessException;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BaseDAOFactory;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.exception.PersistenceException;

/**
 * Implementação base de uma página de pesquisa de entidades.
 * 
 * @author Rafael Volpato
 * @date Aug 8, 2011
 * @param <BO> Tipo da entidade da página de pesquisa
 * @deprecated usar {@link CrudPage}
 */
@Deprecated
public abstract class SearchPage<BO extends Persistable> extends AbstractFormPage implements DeleteCommandHandler, EditCommandHandler,
        ViewCommandHandler, NewCommandHandler, SearchCommandHandler<BO>, TableCommandsProvider {

    private final SpringBeanReference<BaseDAOFactory> daoFactoryRef = SpringBeanReference.forName("daoFactory");

    private Form<BO> form;

    /**
     * Construtor.
     */
    public SearchPage() {
        super();

        this.generateComponents();
    }

    /**
     * Construtor.
     * 
     * @param pageParameters {@link PageParameters}
     */
    public SearchPage(PageParameters pageParameters) {
        super(pageParameters);

        this.generateComponents();
    }

    /**
     * Gera os principais componentes da página.
     */
    private void generateComponents() {
        this.form = new Form<BO>("mainForm");
        this.form.setModel(new CompoundPropertyModel<BO>(this.createEntity()));
        this.add(new FeedbackPanel("feedback"));

        this.form.add(new ButtonCommandsPanel("pageCommands", this.getPageCommands()));

        ReportBuilder reportBuilder = new ReportBuilder(LABEL_SEARCH_RESULT.create().getMessage(), this.getResultFileName());
        ReportTableBuilder<BO> tableBuilder = reportBuilder.createTableBuilder(this.getCRUDClass(), "pesquisa");
        this.addResultTableColumns(tableBuilder);
        tableBuilder.addNumberColumn("pk", "", "pk", 0).getParameters().put(PARAM_HIDDEN, "true");
        tableBuilder.close();

        Panel searchResultPanel = this.createSearchResultPanel("pesquisa", reportBuilder);
        searchResultPanel.setOutputMarkupId(true);
        searchResultPanel.setVisible(true);
        this.form.addOrReplace(searchResultPanel);

        this.form.add(new Label("searchTitle", LABEL_SEARCH_TITLE.create(getPageName()).getMessage()));
        this.add(this.form);
        this.addSearchFields(this.form);
    }

    /**
     * Cria um painel que vai ser responsável por renderizar o resultado da pesquisa.
     * 
     * @param id id
     * @param reportBuilder {@link ReportBuilder}
     * @return {@link Panel}
     */
    protected Panel createSearchResultPanel(String id, ReportBuilder reportBuilder) {
        return new SearchResultTable<BO>(id, reportBuilder.createReport(), this.getCRUDClass(), this, this);
    }

    /**
     * Cria uma nova instância da entidade.
     * 
     * @return uma nova instancia da entidade
     */
    private BO createEntity() {
        BaseDAO<BO> dao = this.daoFactoryRef.get().getGenericDAO(this.getCRUDClass());
        BO entity = dao.createBean();
        this.fillSearchEntity(entity);
        return entity;
    }

    /**
     * Retorna a classe da entidade que esta sendo manipulada por esta página.
     * 
     * @return a classe da entidade que esta sendo manipulada por esta página
     */
    @SuppressWarnings("unchecked")
    public Class<BO> getCRUDClass() {
        try {
            return (Class<BO>) GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(
                    (ParameterizedType) this.getClass().getGenericSuperclass(), this.getClass(), 0);
        } catch (IllegalArgumentException e) {
            throw new PersistenceException(PersistMessages.INVALID_BEAN_IMPLEMENTATION.create(this.getClass()));
        }
    }

    /**
     * Exclui a entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser excluida.
     * @return <code>null</code>
     */
    public final Page createDeletePage(Serializable entityPK) {
        BO entity = null;
        try {
            entity = this.loadEntity(entityPK);
        } catch (BeanNotFoundException e) {
            throw new BusinessException(ERROR_CANNOT_FIND_ENTITY_TO_DELETE.create(getPageName(), entityPK));
        }
        entity.getPersister().remove();
        this.info(INFO_ENTITY_DELETE_SUCCESS.create().getMessage());
        return null;
    }

    /**
     * Redireciona para a página de edição da entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser editada.
     * @return página para onde deve ser direcionado para editar a entidade.
     */
    public final Page createEditPage(Serializable entityPK) {
        try {
            return this.createFormPage(this.loadEntity(entityPK), FormType.EDIT);
        } catch (BeanNotFoundException e) {
            throw new BusinessException(ERROR_CANNOT_FIND_ENTITY_TO_EDIT.create(getPageName(), entityPK));
        }
    }

    /**
     * Redireciona para a página de visualização da entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser exibida.
     * @return página para onde deve ser direcionado para visualizar a entidade.
     */
    public final Page createViewPage(Serializable entityPK) {
        try {
            return this.createFormPage(this.loadEntity(entityPK), FormType.VIEW);
        } catch (BeanNotFoundException e) {
            throw new BusinessException(ERROR_CANNOT_FIND_ENTITY_TO_VIEW.create(getPageName(), entityPK));
        }
    }

    /**
     * Carrega a entidade do banco de dados.
     * 
     * @param entityPK pk da entidade
     * @return entidade de PK informada
     * @throws BeanNotFoundException se não existir nenhuma entidade com o PK informado no banco de dados
     */
    protected final BO loadEntity(Serializable entityPK) throws BeanNotFoundException {
        BaseDAO<BO> dao = this.daoFactoryRef.get().getGenericDAO(this.getCRUDClass());
        return dao.findByPk(entityPK);
    }

    /**
     * Retorna a entidade associada ao formulario.
     * 
     * @return a entidade associada ao formulario
     */
    protected final BO getEntity() {
        return this.form.getModelObject();
    }

    /**
     * Hook para preencher a entidade de pesquisa.
     * 
     * @param entity entity
     */
    protected void fillSearchEntity(BO entity) {
        // Hook
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @return lista de comandos que devem ser exibidos
     */
    protected List<ButtonCommand> getPageCommands() {
        List<ButtonCommand> commands = new ArrayList<ButtonCommand>();
        commands.add(new SearchCommand());
        commands.add(new NewCommand(this));
        return commands;
    }

    /**
     * {@inheritDoc}
     */
    public boolean shouldIncludeTableCommands() {
        return true;
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @param entityPK entityPK
     * @return lista de comandos que devem ser exibidos
     */
    public final List<ImageCommand> getTableCommands(Long entityPK) {
        try {
            return this.getTableCommands(this.loadEntity(entityPK));
        } catch (BeanNotFoundException e) {
            this.error(ERROR_CANNOT_FIND_ENTITY_TO_VIEW.create(getPageName(), entityPK));
            return Collections.emptyList();
        }
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @param entity entity
     * @return lista de comandos que devem ser exibidos
     */
    protected List<ImageCommand> getTableCommands(BO entity) {
        List<ImageCommand> commands = new ArrayList<ImageCommand>();
        commands.add(new ViewCommand(this));
        commands.add(new EditCommand(this));
        commands.add(new DeleteCommand(this));
        return commands;
    }

    /**
     * Retorna o nome de arquivo padrão que deve ser utilizado para salvar os arquivos exportados por essa página.
     * 
     * @return O nome de arquivo padrão que deve ser utilizado para salvar os arquivos exportados por essa página
     */
    protected String getResultFileName() {
        return "Pesquisa " + getPageName();
    }

    /**
     * {@inheritDoc}
     */
    public void addReportHeader(ReportBuilder reportBuilder) {
        // OK
    }

    /**
     * Inclui os campos de pesquisa no container
     * 
     * @param container container
     */
    protected abstract void addSearchFields(WebMarkupContainer container);

    /**
     * Inclui as colunas que devem ser exibidas na tabela de resultado.
     * 
     * @param table tabela
     */
    protected abstract void addResultTableColumns(ReportTableBuilder<BO> table);

    /**
     * Retorna a página para a visualização/edição de uma entidade.
     * 
     * @param entity entidade que deve ser emixibida/editada
     * @param formType tipo do formulario (visualização/edição)
     * @return a página para a visualização/edição de uma entidade
     */
    protected abstract Page createFormPage(BO entity, FormType formType);
}
